package com.frank.oj.manager;

/**
 * @author frank
 * @Date 2024/4/15
 */

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.extra.emoji.EmojiUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.frank.model.entity.contest.Contest;
import com.frank.model.entity.discussion.Comment;
import com.frank.model.entity.discussion.CommentLike;
import com.frank.model.entity.discussion.Discussion;
import com.frank.model.entity.discussion.Reply;
import com.frank.model.entity.user.UserAcproblem;
import com.frank.oj.annotation.HCOJAccessEnum;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusForbiddenException;
import com.frank.oj.common.exception.StatusNotFoundException;
import com.frank.oj.config.NacosSwitchConfig;
import com.frank.oj.config.SwitchConfig;
import com.frank.oj.exception.AccessException;
import com.frank.oj.model.dto.ReplyDTO;
import com.frank.oj.model.vo.CommentListVO;
import com.frank.oj.model.vo.CommentVO;
import com.frank.oj.model.vo.ReplyVO;
import com.frank.oj.service.entity.contest.ContestEntityService;
import com.frank.oj.service.entity.discussion.CommentEntityService;
import com.frank.oj.service.entity.discussion.DiscussionEntityService;
import com.frank.oj.service.entity.msg.ReplyEntityService;
import com.frank.oj.service.entity.user.UserAcProblemEntityService;
import com.frank.oj.service.oj.CommentLikeEntityService;
import com.frank.oj.shiro.AccountProfile;
import com.frank.oj.validator.AccessValidator;
import com.frank.oj.validator.CommonValidator;
import com.frank.oj.validator.ContestValidator;
import com.frank.oj.validator.GroupValidator;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class CommentManager {
    @Resource
    private DiscussionEntityService discussionEntityService;
    @Resource
    private AccessValidator accessValidator;
    @Resource
    private GroupValidator groupValidator;
    @Resource
    private CommentEntityService commentEntityService;
    @Resource
    private CommentLikeEntityService commentLikeEntityService;
    @Resource
    private ReplyEntityService replyEntityService;

    @Resource
    private UserAcProblemEntityService userAcproblemEntityService;

    @Resource
    private ContestEntityService contestEntityService;

    @Resource
    private ContestValidator contestValidator;

    @Resource
    private CommonValidator commonValidator;

    @Resource
    private NacosSwitchConfig nacosSwitchConfig;

    public CommentListVO getComments(Long cid, Integer did, Integer limit, Integer currentPage) throws AccessException, StatusForbiddenException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");

        if (cid == null && did != null) { //非比赛
            LambdaQueryWrapper<Discussion> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(Discussion::getId, Discussion::getGid).eq(Discussion::getId, did);
            Discussion discussion = discussionEntityService.getOne(queryWrapper);

            if (discussion != null && discussion.getGid() != null) { //团队
                accessValidator.validateAccess(HCOJAccessEnum.GROUP_DISCUSSION);
                if (!isRoot && !groupValidator.isGroupMember(userRole.getUid(), discussion.getGid())) {
                    throw new StatusForbiddenException("对不起，您没有权限访问该讨论区");
                }
            } else {
                accessValidator.validateAccess(HCOJAccessEnum.PUBLIC_DISCUSSION);
            }
        } else { //比赛
            accessValidator.validateAccess(HCOJAccessEnum.CONTEST_COMMENT);
        }
        IPage<CommentVO> commentList = commentEntityService.getCommentList(limit, currentPage, cid, did, isRoot, userRole != null ? userRole.getUid() : null);

        HashMap<Integer, Boolean> commentLikeMap = new HashMap<>();
        if (userRole != null) {
            LinkedList<Integer> commentIdList = new LinkedList<>();

            for (CommentVO commentVO : commentList.getRecords()) {
                commentIdList.add(commentVO.getId());
            }
            if (!commentIdList.isEmpty()) {
                LambdaQueryWrapper<CommentLike> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.in(CommentLike::getCid, commentIdList);

                List<CommentLike> commentLikeList = commentLikeEntityService.list(queryWrapper);

                for (CommentLike commentLike : commentLikeList) {
                    commentLikeMap.put(commentLike.getCid(), true);
                }
            }
        }
        CommentListVO commentListVO = new CommentListVO();
        commentListVO.setCommentLikeMap(commentLikeMap);
        commentListVO.setCommentList(commentList);

        return commentListVO;
    }

    @Transactional(rollbackFor = Exception.class)
    public CommentVO addComment(Comment comment) throws StatusFailException, AccessException, StatusForbiddenException {
        commonValidator.validateContent(comment.getContent(), "评论", 10000);

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        boolean isAdmin = SecurityUtils.getSubject().hasRole("admin");

        Long cid = comment.getCid();
        if (cid == null) {
            LambdaQueryWrapper<Discussion> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(Discussion::getId, Discussion::getGid).eq(Discussion::getId, comment.getDid());
            Discussion discussion = discussionEntityService.getOne(queryWrapper);
            if (discussion == null) {
                throw new StatusFailException("该讨论不存在，无法评论！");
            }

            Long gid = discussion.getGid();
            if (gid != null) { // 团队内部评论
                accessValidator.validateAccess(HCOJAccessEnum.GROUP_DISCUSSION);
                if (!isRoot && !groupValidator.isGroupMember(userRole.getUid(), gid)) {
                    throw new StatusForbiddenException("对不起，您没有操作权限！");
                }
            } else {
                accessValidator.validateAccess(HCOJAccessEnum.PUBLIC_DISCUSSION);
            }

            commentOnConditions();
        } else { //比赛
            accessValidator.validateAccess(HCOJAccessEnum.CONTEST_COMMENT);
            Contest contest = contestEntityService.getById(cid);
            if (contest == null) {
                throw new StatusFailException("比赛不存在，无法发布评论！");
            }
            contestValidator.validateContestAuth(contest, userRole, isRoot);
        }
        comment.setFromAvatar(userRole.getAvatar())
                .setFromName(userRole.getUsername())
                .setFromUid(userRole.getUid());

        if (SecurityUtils.getSubject().hasRole("root")) {
            comment.setFromRole("root");
        } else if (SecurityUtils.getSubject().hasRole("admin")
                || SecurityUtils.getSubject().hasRole("problem_admin")) {
            comment.setFromRole("admin");
        } else {
            comment.setFromRole("user");
        }

        // 带有表情的字符串转换为编码
        comment.setContent(EmojiUtil.toHtml(formatContentRemoveAutoPlay(comment.getContent())));

        boolean saved = commentEntityService.saveOrUpdate(comment);
        if (saved) {
            CommentVO commentVo = new CommentVO();
            commentVo.setContent(comment.getContent());
            commentVo.setId(comment.getId());
            commentVo.setFromAvatar(comment.getFromAvatar());
            commentVo.setFromName(comment.getFromName());
            commentVo.setFromUid(comment.getFromUid());
            commentVo.setLikeNum(0);
            commentVo.setGmtCreate(comment.getGmtCreate());
            commentVo.setReplyList(new LinkedList<>());
            commentVo.setFromTitleName(userRole.getTitleName());
            commentVo.setFromTitleColor(userRole.getTitleColor());

            //    讨论区回复：统计回复数
            if (comment.getDid() != null) {
                Discussion discussion = discussionEntityService.getById(comment.getDid());
                if (discussion != null) {
                    discussion.setCommentNum(discussion.getCommentNum() + 1);
                    discussionEntityService.updateById(discussion);

                    commentEntityService.updateCommentMsg(discussion.getUid(),
                            userRole.getUid(),
                            comment.getContent(),
                            comment.getDid(),
                            discussion.getGid());
                }
            }
            return commentVo;
        } else {
            throw new StatusFailException("评论失败！");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteComment(Comment comment) throws StatusFailException, AccessException, StatusForbiddenException {
        commonValidator.validateNotEmpty(comment.getId(), "评论ID");

        comment = commentEntityService.getById(comment.getId());
        if (comment == null) {
            throw new StatusFailException("删除失败，当前评论已不存在！");
        }

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        boolean isAdmin = SecurityUtils.getSubject().hasRole("admin");

        Long cid = comment.getCid();
        if (cid == null) {
            LambdaQueryWrapper<Discussion> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(Discussion::getId, Discussion::getGid).eq(Discussion::getId, comment.getDid());
            Discussion discussion = discussionEntityService.getOne(queryWrapper);
            Long gid = discussion.getGid();
            if (gid == null) {
                accessValidator.validateAccess(HCOJAccessEnum.PUBLIC_DISCUSSION);
                if (!comment.getFromUid().equals(userRole.getUid()) && !isRoot && !isProblemAdmin && !isAdmin) {
                    throw new StatusForbiddenException("非本人无权删除该评论！");
                }
            } else {
                accessValidator.validateAccess(HCOJAccessEnum.GROUP_DISCUSSION);
                if (!groupValidator.isGroupAdmin(userRole.getUid(), gid)
                        && !comment.getFromUid().equals(userRole.getUid())
                        && !isRoot) {
                    throw new StatusForbiddenException("无权删除该评论");
                }
            }
        } else {
            accessValidator.validateAccess(HCOJAccessEnum.CONTEST_COMMENT);
            Contest contest = contestEntityService.getById(cid);
            Long gid = contest.getGid();
            if (!comment.getFromUid().equals(userRole.getUid())
                    && !isRoot
                    && !contest.getUid().equals(userRole.getUid())
                    && !(contest.getIsGroup() && groupValidator.isGroupRoot(userRole.getUid(), gid))) {
                throw new StatusForbiddenException("无权删除该评论");
            }
        }
        // 删除评论会同时删除它里面的回复内容
        int replyNum = replyEntityService.count(new LambdaQueryWrapper<Reply>().eq(Reply::getCommentId, comment.getId()));
        // 删评论
        boolean removed = commentEntityService.removeById(comment.getId());
        // 清除要删除的评论的回复数据
        replyEntityService.remove(new LambdaQueryWrapper<Reply>().eq(Reply::getCommentId, comment.getId()));

        if (removed) {
            if (comment.getDid() != null) {
                LambdaUpdateWrapper<Discussion> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.eq(Discussion::getId, comment.getDid()).setSql("comment_num=comment_num-" + (replyNum + 1));
                discussionEntityService.update(updateWrapper);
            }
        } else {
            throw new StatusFailException("删除失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void addCommentLike(Integer cid, Boolean toLike, Integer sourceId, String sourceType) throws StatusFailException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        QueryWrapper<CommentLike> commentLikeQueryWrapper = new QueryWrapper<>();
        commentLikeQueryWrapper.eq("cid", cid).eq("uid", userRole.getUid());

        CommentLike commentLike = commentLikeEntityService.getOne(commentLikeQueryWrapper, false);

        if (toLike) {
            if (commentLike == null) {
                boolean saved = commentLikeEntityService.saveOrUpdate(new CommentLike()
                        .setUid(userRole.getUid())
                        .setCid(cid));

                if (!saved) {
                    throw new StatusFailException("点赞出现异常");
                }
            }
            Comment comment = commentEntityService.getById(cid);
            if (comment != null) {
                comment.setLikeNum(comment.getLikeNum() + 1);
                commentEntityService.updateById(comment);

                if (!userRole.getUid().equals(comment.getFromUid())) {
                    commentEntityService.updateCommentLiseMsg(comment.getFromUid(), userRole.getUid(), sourceId, sourceType);
                }
            }
        } else {
            if (commentLike != null) {
                boolean removed = commentLikeEntityService.removeById(commentLike.getId());
                if (!removed) {
                    throw new StatusFailException("取消点赞失败，请重试尝试！");
                }
            }
            UpdateWrapper<Comment> commentUpdateWrapper = new UpdateWrapper<>();
            commentUpdateWrapper.setSql("like_num=like_num-1").eq("id", cid);
            commentEntityService.update(commentUpdateWrapper);
        }
    }

    public List<ReplyVO> getAllReply(Integer commentId, Long cid) throws AccessException, StatusForbiddenException, StatusFailException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");

        if (cid == null) {
            Comment comment = commentEntityService.getById(commentId);
            LambdaQueryWrapper<Discussion> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(Discussion::getId, Discussion::getGid).eq(Discussion::getId, comment.getDid());
            Discussion discussion = discussionEntityService.getOne(queryWrapper);
            Long gid = discussion.getGid();
            if (gid != null) {
                accessValidator.validateAccess(HCOJAccessEnum.GROUP_DISCUSSION);
                if (!isRoot && !groupValidator.isGroupMember(userRole.getUid(), gid)) {
                    throw new StatusForbiddenException("对不起，您无权限操作！");
                }
            } else {
                accessValidator.validateAccess(HCOJAccessEnum.PUBLIC_DISCUSSION);
            }
        } else {
            accessValidator.validateAccess(HCOJAccessEnum.CONTEST_COMMENT);
            Contest contest = contestEntityService.getById(cid);
            contestValidator.validateContestAuth(contest, userRole, isRoot);
        }

        return replyEntityService.getAllReplyByCommentId(cid, userRole != null ? userRole.getUid() : null, isRoot, commentId);
    }

    public ReplyVO addReply(ReplyDTO replyDto) throws StatusFailException, AccessException, StatusForbiddenException {
        commonValidator.validateContent(replyDto.getReply().getContent(), "回复", 10000);

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        boolean isAdmin = SecurityUtils.getSubject().hasRole("admin");

        Reply reply = replyDto.getReply();
        if (reply == null || reply.getCommentId() == null) {
            throw new StatusFailException("回复失败，当前请求的参数错误！");
        }

        Comment comment = commentEntityService.getById(reply.getCommentId());
        if (comment == null) {
            throw new StatusFailException("当前评论已不存在！");
        }

        Long cid = comment.getCid();
        if (cid == null) {
            LambdaQueryWrapper<Discussion> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(Discussion::getId, Discussion::getGid).eq(Discussion::getId, comment.getDid());
            Discussion discussion = discussionEntityService.getOne(queryWrapper);

            Long gid = discussion.getGid();
            if (gid != null) {
                accessValidator.validateAccess(HCOJAccessEnum.GROUP_DISCUSSION);
                if (!groupValidator.isGroupMember(userRole.getUid(), gid) && !isRoot) {
                    throw new StatusForbiddenException("对不起，您无权限回复！");
                }
            } else {
                accessValidator.validateAccess(HCOJAccessEnum.PUBLIC_DISCUSSION);
            }
            commentOnConditions();
        } else {
            accessValidator.validateAccess(HCOJAccessEnum.CONTEST_COMMENT);
            Contest contest = contestEntityService.getById(cid);
            Long gid = contest.getGid();
            if (!comment.getFromUid().equals(userRole.getUid())
                    && !isRoot
                    && !contest.getUid().equals(userRole.getUid())
                    && !(contest.getIsGroup() && groupValidator.isGroupRoot(userRole.getUid(), gid))) {
                throw new StatusForbiddenException("对不起，您无权限回复！");
            }
        }

        reply.setFromAvatar(userRole.getAvatar())
                .setFromName(userRole.getUsername())
                .setFromUid(userRole.getUid());
        if (SecurityUtils.getSubject().hasRole("root")) {
            reply.setFromRole("root");
        } else if (SecurityUtils.getSubject().hasRole("admin")
                || SecurityUtils.getSubject().hasRole("problem_admin")) {
            reply.setFromRole("admin");
        } else {
            reply.setFromRole("user");
        }
        reply.setContent(EmojiUtil.toHtml(formatContentRemoveAutoPlay(reply.getContent())));

        boolean saved = replyEntityService.saveOrUpdate(reply);
        if (saved) {
            if (replyDto.getDid() != null) {
                UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();
                discussionUpdateWrapper.eq("id", replyDto.getDid())
                        .setSql("comment_num=comment_num+1");
                discussionEntityService.update(discussionUpdateWrapper);

                replyEntityService.updateReplyMsg(replyDto.getDid(),
                        "Discussion",
                        reply.getContent(),
                        replyDto.getQuoteId(),
                        replyDto.getQuoteType(),
                        reply.getToUid(),
                        reply.getFromUid());
            }
            ReplyVO replyVO = new ReplyVO();
            BeanUtil.copyProperties(reply, replyVO);
            replyVO.setFromTitleName(userRole.getTitleName());
            replyVO.setFromTitleColor(userRole.getTitleColor());

            return replyVO;
        } else {
            throw new StatusFailException("回复异常！");
        }
    }


    public void deleteReply(ReplyDTO replyDto) throws StatusFailException, StatusForbiddenException, AccessException {
        Reply reply = replyDto.getReply();

        if (reply == null || reply.getId() == null) {
            throw new StatusFailException("删除失败，删除的回复不可为空！");
        }
        reply = replyEntityService.getById(reply.getId());
        if (reply == null) {
            throw new StatusFailException("删除失败，当前回复的数据已不存在！");
        }
        Comment comment = commentEntityService.getById(reply.getCommentId());
        if (comment == null) {
            throw new StatusFailException("删除失败，当前回复所属的评论已不存在！");
        }

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        boolean isAdmin = SecurityUtils.getSubject().hasRole("admin");

        Long cid = comment.getCid();
        if (cid == null) {
            Discussion discussion = discussionEntityService.getById(comment.getDid());
            Long gid = discussion.getGid();
            if (gid == null) {
                accessValidator.validateAccess(HCOJAccessEnum.PUBLIC_DISCUSSION);
                if (!reply.getFromUid().equals(userRole.getUid())
                        && !isRoot
                        && !isProblemAdmin
                        && !isAdmin) {
                    throw new StatusForbiddenException("无权删除该回复");
                }
            } else {
                accessValidator.validateAccess(HCOJAccessEnum.GROUP_DISCUSSION);
                if (!reply.getFromUid().equals(userRole.getUid())
                        && !isRoot
                        && !groupValidator.isGroupAdmin(userRole.getUid(), gid)) {
                    throw new StatusForbiddenException("无权删除该回复");
                }
            }
        } else {
            accessValidator.validateAccess(HCOJAccessEnum.CONTEST_COMMENT);
            Contest contest = contestEntityService.getById(cid);
            if (!reply.getFromUid().equals(userRole.getUid())
                    && !isRoot
                    && !contest.getUid().equals(userRole.getUid())
                    && !(contest.getIsGroup() && groupValidator.isGroupRoot(userRole.getUid(), contest.getGid()))) {
                throw new StatusForbiddenException("无权删除该回复");
            }
        }

        boolean isOk = replyEntityService.removeById(reply.getId());
        if (isOk) {
            // 如果是讨论区的回复，删除成功需要减少统计该讨论的回复数
            if (replyDto.getDid() != null) {
                UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<>();
                discussionUpdateWrapper.eq("id", replyDto.getDid())
                        .setSql("comment_num=comment_num-1");
                discussionEntityService.update(discussionUpdateWrapper);
            }
        } else {
            throw new StatusFailException("删除失败，请重新尝试");
        }
    }

    /**
     * 评论限制
     */
    private void commentOnConditions() throws StatusForbiddenException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        boolean isAdmin = SecurityUtils.getSubject().hasRole("admin");

        if (!isRoot && !isProblemAdmin && !isAdmin) {
            QueryWrapper<UserAcproblem> wrapper = new QueryWrapper<>();
            wrapper.eq("uid", userRole.getUid()).select("distinct pid");
            int userAcProblemCount = userAcproblemEntityService.count(wrapper);

            SwitchConfig switchConfig = nacosSwitchConfig.getSwitchConfig();
            if (userAcProblemCount < switchConfig.getDefaultCreateCommentACInitValue()) {
                throw new StatusForbiddenException("对不起，该账户评论功能暂未激活！系统设定提交题目通过"
                        + switchConfig.getDefaultCreateDiscussionACInitValue() + "道以上才可评论！加油💪");
            }
        }
    }

    private final static Pattern pattern = Pattern.compile("<.*?([a,A][u,U][t,T][o,O][p,P][l,L][a,A][y,Y]).*?>");

    private String formatContentRemoveAutoPlay(String content) {
        StringBuilder stringBuilder = new StringBuilder(content);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            stringBuilder.replace(matcher.start(1), matcher.end(1), "controls");
        }
        return stringBuilder.toString();
    }
}
